home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / SWAG / SWAGA_C / ARCHIVES.SWG / 0026_Delete all files EXCEPT ZIP.pas < prev    next >
Pascal/Delphi Source File  |  1995-03-03  |  19KB  |  687 lines

  1. {
  2.           ZNDEL version 2.1   -  Public Domain / Freeware
  3.  
  4.         Exclusive-Delete utility ( originally 'ZIP-NOT-DEL' ? )
  5.  
  6.               E. de Neve    CompuServe ID: 100121,1070
  7.  
  8.  
  9.    Version 2.1  November 1, 1994
  10.  
  11.    New in version 2.1
  12.      - fixed bug in redirection detection
  13.      - confirmation prompt will now bypass redirection
  14.  
  15.  
  16.    Version 2.0  August 17, 1994
  17.  
  18.    New in version 2.0 :
  19.      - recognizes 12 of the most common archive format extensions
  20.      - full DIR-style wildcard support
  21.      - confirmation asked before deleting
  22.      - no confirmation needed in assigned working directories
  23.      - realistic limits & safety checks for maximum number of files
  24.      - switch to override prompting, useful in batch files
  25.  
  26.  
  27.    Version 1.0  (Original)  Written  Sept. 21, 1991  by  G. Palmer
  28.  
  29. }
  30.  
  31. {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-} {* compiler switches *}
  32. Program Zndel2;
  33.  
  34. Uses Dos, Crt;
  35.  
  36. Type
  37.   FullNameStr = String [12];
  38.  
  39. Const
  40.   Assume_Yes: Boolean = False;
  41.  
  42.   Maxdelete   =  2000;
  43.   Maxsave     =  32;
  44.   Maxworkdirs =  32;
  45.  
  46.   MetaBufSize =  4000;  { I/O buffer used when patching .exe file }
  47.  
  48.   ConfigStart: String [5] = '(CFG<'; { mark start of config area }
  49.   Nr_workdirs: Byte =  0;
  50.   Workdirs: Array [1..MaxWorkDirs] Of FullNameStr =
  51.   ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
  52.   '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
  53.   ConfigEnd: String [5] = '>CFG)'; { end of config area }
  54.  
  55.   MaxArchExt  =  12;
  56.   ArchExt: Array [1..MaxArchExt] Of String [3] =
  57.   ( 'ZIP', 'ARJ', 'LZH', 'ARC', 'LIM', 'UC2',
  58.   'PAK', 'SQZ', 'HAP', 'SDN', 'ZOO', 'SIT' );
  59.  
  60.  
  61.   VaLetSet: Set Of Char = [ '#'..')', '!', '@', '^', '~', '_', '{',
  62.   '}', '-', '0'..'9', 'A'..'Z', 'a'..'z'];
  63.   { set of valid letters that make up an unambiguous file/dir name }
  64.  
  65.  
  66. Var
  67.   CH:                     Char;
  68.   I:                      Word;
  69.   Afile:                  File;
  70.   NormOut:                Text;
  71.   Nr_Names_To_Save:       Word;
  72.   Nr_Files_To_Delete:     Word;
  73.   Nr_Files_Found:         Word;
  74.   Nr_Files_to_Protect:    Word;
  75.   TempStr, UpStr:         FullNameStr;
  76.   Files_To_Delete :       Array [1..maxdelete]   Of FullNameStr;
  77.   Names_To_Save:          Array [1..maxsave]     Of FullNameStr;
  78.   Search_Record:          SearchRec;
  79.   MetaBuffer:             Array [0..MetaBufSize] Of Byte;
  80.  
  81. Procedure Show_Info;
  82. Begin
  83.   WriteLn;
  84.   WriteLn ('Deletes all files in the current directory, except:');
  85.   WriteLn ('        Files listed on the command line, DIR-style wildcards allowed.');
  86.   WriteLn ('        Archived files ( ZIP, LZH, ARC, ARJ, LIM, ZOO etc. )');
  87.   WriteLn ('        Hidden, System and ReadOnly files.');
  88.   WriteLn;
  89.   WriteLn ('Usage:  ZNDEL [/Y] [filespec (filespec) ]  delete all but filespecs & archives');
  90.   WriteLn ('                └──>   assume YES on all prompting');
  91.   WriteLn ('        ZNDEL /S       show current settings');
  92.   WriteLn ('        ZNDEL /?       show this help text');
  93.   WriteLn;
  94.   WriteLn ('        ZNDEL /W  [workdir (workdir) ]   assign working directories ');
  95.   WriteLn;
  96.   WriteLn ('        ZNDEL will always ask for confirmation before deleting files,');
  97.   WriteLn ('        unless the current directory is one of the assigned working dirs.');
  98.   Halt;
  99. End;
  100.  
  101.  
  102. Procedure WildExpand (Var inname: String);
  103.  
  104. Var workname: String [12]; {name}
  105.   Havecard: Boolean;
  106.   S, D, P: Byte;           {counters source,destin,point}
  107.   ic: Char;
  108.  
  109. Procedure PartExpand;
  110. Begin
  111.   If HaveCard Then IC := '?'
  112.   Else
  113.     If (S > Byte (inname [0] ) ) Or (P > 0) Then IC := ' ' Else
  114.     Begin
  115.       IC := UpCase (inname [S] );
  116.       If IC = '*' Then
  117.       Begin Havecard := True; IC := '?'; End
  118.       Else
  119.         If IC = '.' Then
  120.         Begin P := S; IC := ' '; End;
  121.       Inc (S);
  122.     End; {real ic digest}
  123.  
  124.   Workname [D] := IC;
  125.   Inc (D);
  126. End;
  127.  
  128.  
  129. Begin
  130.  
  131.   S := 1; { source }
  132.   D := 1; { destin }
  133.   P := 0; { point-pos }
  134.  
  135.   workname [0] := #12;
  136.   workname [9] := '.';
  137.  
  138.  
  139.   HaveCard := (Inname [0] > #0) And (inname [1] = '.');
  140.  
  141.   While (Byte (inname [0] ) > S) And (Inname [S] = ' ') Do Inc (S);
  142.   { 'remove' front spaces... }
  143.  
  144.   Repeat {copy into name8}
  145.     PartExpand;
  146.   Until D = 9;
  147.  
  148.   S := 1; {FIND any point if it exists..}
  149.   While (P = 0) And (S <= Byte (Inname [0] ) ) Do
  150.   Begin
  151.     If inname [S] = '.' Then P := S Else Inc (S);
  152.   End;
  153.  
  154.   Havecard := ( (P = 0) And (Inname [0] > #0) )  Or (Inname [0] = #1);
  155.  
  156.   S := P; {on point }
  157.   P := 0;
  158.  
  159.   Inc (S); {both get over point}
  160.   Inc (D);
  161.  
  162.   PartExpand; {ext 3 chars}
  163.   PartExpand;
  164.   PartExpand;
  165.  
  166.   Inname := WorkName;
  167. End;
  168.  
  169.  
  170.  
  171. Function MatchWild (Var WW1, SS2: String): Boolean; {count on BOTH being expanded..}
  172. Var CC: Byte;
  173. Begin
  174.  
  175.   {loop both strings, if wild has non-? char that doesnt match SS2 char,
  176.   OR SS2 char has ? that doesn't match SPACE, then exit}
  177.  
  178.   matchwild := False;
  179.  
  180.   For CC := 1 To 12 Do If WW1 [CC] <> SS2 [CC] Then
  181.   Begin
  182.     If ( (ww1 [cc] = ' ') And (ss2 [cc] <> '?') )
  183.        Or
  184.        ( (WW1 [CC] <> '?') )
  185.     Then Exit;
  186.   End;
  187.  
  188.   Matchwild := True;
  189. End;
  190.  
  191.  
  192. Function SameName (Wild, Sample: String): Boolean;
  193. Begin
  194.   { Note: WILD must be an already expanded 13-character wildcard string}
  195.   Wildexpand (Sample);
  196.   Samename := matchwild (Wild, Sample);
  197. End;
  198.  
  199. Procedure Show_Config;
  200. Begin
  201.   Write ('Assigned working directories:   ');
  202.   If Nr_Workdirs = 0 Then WriteLn ('None.');
  203.   For I := 1 To Nr_Workdirs Do Write (Workdirs [I], '  ');
  204.   WriteLn;
  205.   Halt;
  206. End;
  207.  
  208.  
  209. Function ValidDirName (Var Workstring: String): Boolean;
  210. Var I: Byte;
  211.   NumPoints: Byte;
  212.   PointStart: Byte;
  213.   ExtSize: Byte;
  214.   NameSize: Byte;
  215. Begin
  216.   PointStart := 0;
  217.   For I := 1 To Length (WorkString) Do
  218.   Begin
  219.     If (Workstring [i] = '.') And (Pointstart = 0) Then
  220.     Begin {point digest}
  221.       If I > 1 Then PointStart := I
  222.       Else Begin ValidDirName := False; Exit; End;
  223.       {too many points, or starts with point..}
  224.     End
  225.     {no point - then must be valid filename letter}
  226.     Else
  227.       If Not (Workstring [i] In VaLetSet)
  228.       Then Begin ValidDirName := False; Exit; End;
  229.   End;
  230.  
  231.   {finally, check if the extension OR filename are not too big: }
  232.  
  233.   If ( (Pointstart = 0) And (Length (Workstring) > 8) )
  234.      Or ( Pointstart > 9)
  235.      Or ( ( Pointstart > 1) And (Length (WorkString) > (Pointstart + 3) ) )
  236.   Then ValidDirname := False
  237.   Else
  238.     ValidDirName := True;
  239. End;
  240.  
  241.  
  242. Procedure UpcaseString (Var Workstring: String);
  243.   Var I: Byte;
  244.   Begin
  245.     For I := 1 To Length (WorkString) Do WorkString [i] := UpCase (WorkString [i] );
  246.   End;
  247.  
  248.  
  249. Function FindLocation (Var Infile: File;  Sample: String): LongInt;
  250.  
  251.  { universal 'binary file' search routine, works with files }
  252.  { of any length, even if much larger than 64Kb             }
  253.  { searches a file for sample string using the 'Metabuffer' }
  254.  { assumes the file INFILE was already open for reading     }
  255.  
  256. Var I: LongInt;
  257.   J: Word;
  258.   Location: LongInt;
  259.   BytesRead: Word;
  260.   SearchIndex: LongInt;
  261. Begin
  262.  
  263.   SearchIndex := 0;
  264.   FindLocation := 0;
  265.  
  266.   If Length (Sample) = 0 Then Exit;
  267.  
  268.   Repeat
  269.     Seek (InFile, Searchindex);
  270.  
  271.     BlockRead (InFile, Metabuffer, SizeOf (Metabuffer), BytesRead);
  272.  
  273.     If BytesRead < Length (Sample) Then Exit; {file or buffer too small..}
  274.  
  275.     For I := 0 To (BytesRead - Length (Sample) ) Do
  276.       If MetaBuffer [i] = Byte (Sample [1] ) Then
  277.       Begin
  278.         J := 1;
  279.  
  280.         While (J < Length (Sample) ) And
  281.               ( Metabuffer [I + J] = Byte (Sample [J + 1] ) )
  282.         Do Inc (J);
  283.  
  284.         If J = Length (Sample)  Then Begin
  285.           FindLocation := SearchIndex + I;
  286.           Exit;
  287.         End;
  288.  
  289.       End;
  290.  
  291.     If BytesRead < SizeOf (Metabuffer) Then Exit;    { at end of file}
  292.  
  293.     SearchIndex := SearchIndex + BytesRead - Length (Sample) + 1;
  294.  
  295.     { This ensures overlap between consecutive buffer reads; because
  296.     of this overlap, the whole procedure will still work even in
  297.     the extreme case when Sizeof(Metabuffer)=Length(Sample)  !!! }
  298.  
  299.   Until False;
  300.  
  301. End;
  302.  
  303.  
  304.  
  305.  
  306. Procedure Config_Workdirs;
  307.  Var BytesRead, BytesWritten: Word; {dummy args for Blockread/write}
  308.    PatchAddr1, PatchAddr2: Word;
  309.    I, J: Word;
  310.    NewDirs: Word;
  311.    ParamString: String;
  312.  Begin
  313.    { put supplied working dir names into array }
  314.  
  315.    NewDirs := ParamCount;       { First parameter was /W }
  316.  
  317.    Nr_Workdirs := 0;            { disregard old settings }
  318.  
  319.    For i := 2 To NewDirs Do     { expand & add to SAVE specs list }
  320.      If (Nr_workdirs < MaxWorkDirs) Then   { check for max nr of dirs }
  321.      Begin
  322.        ParamString := ParamStr (i);
  323.        UpcaseString (ParamString);
  324.        If ParamString [1] = '/' Then Show_Info; { wrong place for option }
  325.  
  326.        If ValidDirName (ParamString) Then Begin
  327.          Inc (Nr_Workdirs);
  328.          If Paramstring [Byte (Paramstring [0] ) ] = '.' {get rid of ugly points at end}
  329.          Then Dec (Byte (paramstring [0] ) );
  330.          WorkDirs [Nr_Workdirs] := ParamString;
  331.        End;
  332.      End;
  333.  
  334.    { Find 'home' directory, find ZNDEL.EXE (or whatever our name was)
  335.    find out where to insert the new workdirs data structure,
  336.    then copy them to it. }
  337.  
  338.    Assign (Afile, ParamStr (0) ); { it's ME ! }
  339.    FileMode := 2;               { default, read and write possible }
  340.    Reset (Afile, 1);            { open, counting will be done in BYTES }
  341.  
  342.    If IOResult <> 0 Then Begin
  343.      WriteLn ('Configuration failed - file not found.');
  344.      WriteLn;
  345.      Halt;
  346.    End;
  347.  
  348.    PatchAddr1 := FindLocation (Afile, Configstart);
  349.    PatchAddr2 := FindLocation (Afile, ConfigEnd);
  350.  
  351.    If IOResult <> 0 Then Begin
  352.      WriteLn ('Configuration failed - error reading file.');
  353.      WriteLn;
  354.      Halt;
  355.    End;
  356.  
  357.  
  358.    If  (PatchAddr1 = 0) Or (PatchAddr2 = 0)
  359.        Or ( (PatchAddr2 - PatchAddr1) <> (Ofs (ConfigEnd) - Ofs (ConfigStart) ) )
  360.    Then Begin
  361.      WriteLn ('Error - incompatible structure in: ', ParamStr (0) );
  362.      WriteLn;
  363.      Halt;
  364.    End;
  365.  
  366.    { Now seek to config area in file and copy our own data to it.. }
  367.    { The area to patch starts just after 'configstart' at Nr_Workdirs}
  368.  
  369.    Seek (Afile, PatchAddr1 + Length (ConfigStart) );
  370.  
  371.    BlockWrite (Afile, Nr_Workdirs, ( SizeOf (Nr_Workdirs) + SizeOf (Workdirs) ),
  372.    BytesWritten);
  373.  
  374.    Close (Afile);
  375.  
  376.    If IOResult <> 0 Then WriteLn (' Error trying to update options.')
  377.    Else
  378.    Begin
  379.      WriteLn ('New settings written to ', ParamStr (0) );
  380.      Show_Config;
  381.    End;
  382.  
  383.    Halt;
  384.  End;
  385.  
  386.  
  387.  
  388. Procedure Get_Command_Line_Args;
  389. Var
  390.   I:   Word;
  391.   ParamString : String;
  392.   Nr_Params, DigestParam: Byte;
  393.  
  394. Begin
  395.   Nr_Params := ParamCount;
  396.   If Nr_Params = 0  Then Exit;
  397.  
  398.   DigestParam := 1;
  399.  
  400.   ParamString := ParamStr (1);
  401.   UpcaseString (ParamString);
  402.  
  403.   If ParamString = '/W' Then  Config_Workdirs;
  404.   If ParamString = '/S' Then  Show_Config;
  405.  
  406.   If ParamString = '/Y' Then  Begin
  407.     Assume_Yes := True;
  408.     Inc (DigestParam);
  409.   End;
  410.  
  411.   If ParamString = '/?' Then  Show_Info;
  412.  
  413.  
  414.   { no valid options so interpret the rest as filespecs of files to be saved }
  415.  
  416.   Nr_Names_to_save := 0;
  417.  
  418.   For i := DigestParam To Nr_Params Do   { expand & add to SAVE specs list }
  419.     If Nr_Names_to_Save < MaxSave Then         { check for max nr of names }
  420.     Begin
  421.       ParamString := ParamStr (i);
  422.       If ParamString [1] = '/' Then Show_Info;    { wrong place for option }
  423.       WildExpand (ParamString);
  424.       Inc (Nr_Names_to_Save);
  425.       Names_to_Save [Nr_Names_to_save] := ParamString;
  426.     End;
  427.  
  428. End;
  429.  
  430.  
  431.  
  432.  
  433. Procedure Check_If_Protected (Curr_file: String);
  434. Var  I: Integer;
  435. Begin
  436.   Inc (Nr_Files_Found);
  437.   Inc (Nr_Files_to_Protect);  { start and assume it's protected }
  438.  
  439.   If ( (Search_Record.Attr And ReadOnly) = ReadOnly) Then Exit; { Protected }
  440.  
  441.   For I := 1 To MaxArchExt Do              { does it have a known extension? }
  442.     If Pos ('.' + ArchExt [I] , Curr_file) > 1 Then Exit;
  443.  
  444.   For I := 1 To Nr_Names_to_Save Do                 { was it on cmd line? }
  445.     If SameName (Names_to_Save [i], Curr_File)  Then Exit;
  446.  
  447.   Dec (Nr_Files_to_Protect);  { not protected after all }
  448.   Inc (Nr_Files_To_Delete);
  449.  
  450.   Files_To_Delete [ Nr_Files_to_Delete ] := Curr_File;  { add to delete list }
  451.  
  452. End;
  453.  
  454.  
  455. Function InWorkDir: Boolean;
  456. Var ThisDir: String;
  457.   T: Word;
  458. Begin
  459.  
  460.   InWorkDir := True;
  461.  
  462.   { Test if we are in a working dir, or any one of its subdirs.... }
  463.  
  464.   GetDir (0, thisdir);
  465.   Thisdir := Thisdir + '\';
  466.   For T := 1 To Nr_WorkDirs Do
  467.   Begin
  468.     If Pos ('\' + WorkDirs [T] + '\', Thisdir) > 0 Then Exit;
  469.   End;
  470.  
  471.   InWorkDir := False;
  472. End;
  473.  
  474.  
  475. Procedure SayPrott; { ask for confirmation }
  476. Begin
  477.   WriteLn (NormOut,'! WARNING - this is not a known working directory.');
  478.   Write (NormOut,'Are you sure (Y/N)? ');
  479.   While KeyPressed Do CH := ReadKey;
  480.   CH := ReadKey;
  481.   WriteLn (NormOut,CH);
  482.   If UpCase (CH) <> 'Y' Then Halt;
  483. End;
  484.  
  485.  
  486. Procedure Bye;
  487. Begin
  488.   WriteLn;
  489.   WriteLn (NormOut,'ZNDEL 2.1  aborted.  Some files not deleted.');
  490.   Halt;
  491. End;
  492.  
  493.  
  494. Function Redirected: Boolean;
  495.  { detect if user wants redirectable output }
  496. Assembler;
  497. Asm
  498.   MOV   AX, 04400h  { query device info }
  499.   MOV   BX, 1       { for device STDOUT }
  500.   INT   021h
  501.   XOR   AX, AX
  502.   TEST  DL, 1 shl 7 { bit 7 clear: redirected to file }
  503.   JZ   @redirred
  504.   TEST  DL, 1 shl 1 { bit 1 set: device is standard output }
  505.   JNZ  @standard
  506.  @redirred:
  507.   INC   AX          { true if redirected }
  508.  @standard:
  509. End;
  510.  
  511.  
  512. Begin
  513.   AssignCrt(NormOut); { save default mode of screen output to CRT }
  514.     Rewrite(NormOut); { open for writing }
  515.  
  516.   If  Redirected Then  Begin
  517.     {  In Borland/Turbo Pascal, using CRT bypasses DOS so the  }
  518.     {  output is not redirectable. Here we reroute the output  }
  519.     {  to the official DOS STDOUT device again, but only when  }
  520.     {  the user wanted to redirect the output.                 }
  521.     Assign  (Output, '');  { Put pascal output back on real STDOUT.. }
  522.     Rewrite (Output);      { Open for writing }
  523.   End;
  524.  
  525.   While KeyPressed Do CH := ReadKey;
  526.  
  527.   WriteLn ('ZNDEL 2.1  Exclusive Delete utility by G. Palmer and E. de Neve    Freeware');
  528.  
  529.   Get_Command_Line_Args;
  530.  
  531.   If (Not InWorkDir) Then If (Not Assume_Yes) Then SayPrott;
  532.  
  533.   Nr_Files_Found      := 0;
  534.   Nr_Files_to_Protect := 0;
  535.   Nr_Files_to_Delete  := 0;
  536.  
  537.   { Reading directory .. }
  538.  
  539.   FindFirst ('*.*', Archive, Search_Record);
  540.   If (DosError = 0) Then Check_if_protected (Search_Record.Name);
  541.  
  542.   While (DosError = 0) And (Nr_Files_to_Delete < Maxdelete)
  543.   Do Begin
  544.     FindNext (Search_Record);
  545.     If (DosError = 0) Then Check_if_protected (Search_Record.Name);
  546.     If KeyPressed Then bye;       { chance to cancel }
  547.   End;
  548.  
  549.   {   Deleting files .. }
  550.  
  551.   If (Nr_Files_to_Delete > 0 ) Then
  552.   Begin
  553.     If KeyPressed Then bye;       { chance to cancel }
  554.  
  555.     For I := 1 To Nr_Files_To_Delete Do
  556.     Begin
  557.       If KeyPressed Then bye;     { chance to cancel }
  558.       Assign (Afile, Files_To_Delete [I] );
  559.       Erase (Afile);
  560.     End;
  561.  
  562.   End;
  563.  
  564.   WriteLn;
  565.   WriteLn ('    Files found: ', Nr_Files_found);
  566.   WriteLn ('Protected files: ', Nr_Files_to_Protect);
  567.   WriteLn ('  Files deleted: ', Nr_Files_to_Delete);
  568.  
  569. End.
  570.  
  571. { -------------------------------------------------------------------------}
  572.  
  573.          ZNDEL version 2.1   -  Public Domain / Freeware
  574.  
  575.      Exclusive-Delete utility ( originally 'Zip-Not-DEL' ? )
  576.  
  577.      This program deletes all the files in the current directory
  578.      except archives, files specified on the command line, and
  579.      files marked as system, hidden, or read-only.
  580.  
  581.      Very convenient for cleaning up after de-archiving, e.g in
  582.      working- and download directories.
  583.  
  584.  
  585.  Usage:
  586.  
  587.      ZNDEL [/Y] [filespec (filespec) ]  delete all but filespecs & archives
  588.              └──>   assume YES on all prompting (useful in batch files)
  589.  
  590.      ZNDEL /S                           show current workdir assignments
  591.      ZNDEL /W  [workdir (workdir) ]     assign working directories
  592.  
  593.      ZNDEL /?                           show the help text
  594.  
  595.  
  596.  Examples:
  597.  
  598.      delete all but the assembler sources       ZNDEL *.asm
  599.      to keep prog1.pas, prog2.txt etc.          ZNDEL prog?
  600.      combined effect of above examples          ZNDEL prog? *.asm
  601.      the same, without prompting                ZNDEL /Y prog? *.asm
  602.  
  603.  
  604.  Configuration:
  605.  
  606.      You can configure ZNDEL to work automatically without
  607.      prompting for quick cleanups in specific directories.
  608.      Make sure to specify only simple directory names, do not
  609.      include drive ID's or subdirectories, for example:
  610.  
  611.      ZNDEL /W  download  stuff  temp
  612.  
  613.      This makes ZNDEL recognize these directories or any of
  614.      their subdirectories as special working directories, in
  615.      which ZNDEL will never ask for confirmation.
  616.  
  617.      The commands  C:\DOWNLOAD\GAME> ZNDEL
  618.  
  619.               and  C:\COMMPROG\STUFF\MISC> ZNDEL
  620.  
  621.      will both work without confirmation because GAME is a
  622.      subdirectory of DOWNLOAD, and MISC is a subdir of STUFF.
  623.  
  624.  
  625.  Tech notes & details:
  626.  
  627.    The included source file ZNDEL.PAS was tested and
  628.    compiled using Borland Pascal 7.0.
  629.  
  630.    For configuration, the .EXE file itself is modified,
  631.    which will not work when it is compressed by an
  632.    executable compressor like LZEXE or PKLITE.
  633.    Configuration will work OK when ZNDEL.EXE has
  634.    been renamed.
  635.  
  636.    Because wildcards in ZNDEL are used to specify files
  637.    to save rather than files to delete, the DIR wildcard
  638.    convention, which is much more flexible than the
  639.    DEL wildcard (= internal MS-DOS) convention, is
  640.    simulated with all its details and quirks.
  641.    For example, in DIR style, "." and "*" both mean "*.*",
  642.    and "progname" means "progname.*".
  643.  
  644.    All output can be suppressed or redirected, e.g. by
  645.    redirecting to the NUL device, as in  ZNDEL /Y  > NUL
  646.  
  647.    Pascal programmers may find some of the code useful for
  648.    their own programs, especially the redirection routines,
  649.    the self-modification trick including a "binary file search"
  650.    routine (which works on files of unlimited size) and the
  651.    wildcard evaluation. Use the code any way you like.
  652.  
  653.  
  654.  Legal stuff:
  655.  
  656.    There is no warranty of this software's suitability for
  657.    any purpose, nor any acceptance of liability, express or
  658.    implied. By using this free software, you agree to this.
  659.  
  660.  
  661.  Version history:
  662.  
  663.    Version 2.1  November 1, 1994
  664.  
  665.    New in version 2.1
  666.      - fixed bug in redirection detection
  667.      - confirmation prompt will now bypass redirection
  668.  
  669.  
  670.    Version 2.0  August 17, 1994
  671.  
  672.    New in version 2.0 :
  673.      - recognizes 12 of the most common archive format extensions
  674.      - full DIR-style wildcard support
  675.      - confirmation asked before deleting
  676.      - no confirmation needed in assigned working directories
  677.      - realistic limits & safety checks for maximum number of files
  678.      - switch to override prompting, useful in batch files
  679.  
  680.  
  681.    Version 1.0  (Original)  Written  Sept. 21, 1991  by  G. Palmer
  682.  
  683.  
  684.    Original:  Written Sept 21, 1991  by  G.Palmer
  685.  
  686.  
  687.